package test.itachi;

/**
 * @author Itachi
 * @date 2021.06
 */
public class CrcCalculator {

    public AlgoParams Parameters;
    public byte hashSize = 8;
    private long _mask = 0xFFFFFFFFFFFFFFFFL;
    private long[] _table = new long[256];


    // 构造函数
    CrcCalculator(AlgoParams params) {
        Parameters = params;

        hashSize = (byte) params.HashSize;
        if (hashSize < 64) {
            _mask = (1L << hashSize) - 1;
        }

        createTable();
    }

    /**
     * @param data
     * @param length
     * @param offset
     * @return long
     * @author Itachi
     * @date 2021.06
     */
    public long calc(byte[] data, int offset, int length) {
        long init = Parameters.RefOut ? CrcHelper.reverseBits(Parameters.Init, hashSize) : Parameters.Init;
        long hash = computeCrc(init, data, offset, length);
        return (hash ^ Parameters.XorOut) & _mask;
    }

    /**
     * @param init
     * @param data
     * @param length
     * @param offset
     * @return long
     * @author Itachi
     * @date 2021.06
     */
    private long computeCrc(long init, byte[] data, int offset, int length) {
        long crc = init;

        if (Parameters.RefOut) {
            for (int i = offset; i < offset + length; i++) {
                crc = (_table[(int) ((crc ^ data[i]) & 0xFF)] ^ (crc >>> 8));
                crc &= _mask;
            }
        } else {
            int toRight = (hashSize - 8);
            toRight = toRight < 0 ? 0 : toRight;
            for (int i = offset; i < offset + length; i++) {
                crc = (_table[(int) (((crc >> toRight) ^ data[i]) & 0xFF)] ^ (crc << 8));
                crc &= _mask;
            }
        }

        return crc;
    }

    /**
     * @author Itachi
     * @date 2021.06
     */
    private void createTable() {
        for (int i = 0; i < _table.length; i++) {
            _table[i] = createTableEntry(i);
        }
    }

    /**
     * @param index
     * @author Itachi
     * @date 2021.06
     */
    private long createTableEntry(int index) {
        long r = (long) index;

        if (Parameters.RefIn) {
            r = CrcHelper.reverseBits(r, hashSize);
        } else if (hashSize > 8) {
            r <<= (hashSize - 8);
        }

        long lastBit = (1L << (hashSize - 1));

        for (int i = 0; i < 8; i++) {
            if ((r & lastBit) != 0) {
                r = ((r << 1) ^ Parameters.Poly);
            } else {
                r <<= 1;
            }
        }

        if (Parameters.RefOut) {
            r = CrcHelper.reverseBits(r, hashSize);
        }

        return r & _mask;
    }
}
